package pt.isel.pdm.ouryamba;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

import winterwell.jtwitter.Twitter;
import android.app.Application;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

public class General extends Application implements IMyListener
{
	//TODO DEFAULT_URL = "http://yamba.marakana.com/api";
	public SharedPreferences prefs;
	private Integer maxChars;
	private Integer maxMsgToShow;
	private Twitter twitter;
	public final String TAG = "PDM";
	public int statusSelectedPosition;
	public final Integer MAXIMUM_STATUS_LENGTH = 140;
	public final Integer DEFAULT_MSG_TO_SHOW_NUMBER = 20;
	public final Integer DEFAULT_MSG_TO_STORE_NUMBER = 20;
	public final Integer DEFAULT_AUTOMATIC_UPDATE_INTERVAL = 1;
	public final Integer CONST_ONE_MIN_IN_MILIS = 1000;
	public final String STATUS_LENGTH_PREF_KEY = "lengthPref";
	public final String USER_PREF_KEY = "userPref";
	public final String PASSWORD_PREF_KEY = "passPref";
	public final String URL_PREF_KEY = "urlPref";
	public final String MSG_NUMBER_TO_SHOW_PREF_KEY = "msgNumberPref";
	public final String MSG_NUMBER_TO_STORE_PREF_KEY = "numberOfMessages2StorePref";
	public final String AUTOMATIC_UPDATE_PREF_KEY = "automaticUpdatePref";
	public final String AUTOMATIC_UPDATE_PERIOD_PREF_KEY = "automaticUpdatePeriodPref";
	public final String EMPTY_STRING = "";
	public final String SPACE_STRING = " ";
	public ConcurrentLinkedQueue<IMyListener> toNotifyList;
	public volatile List<Twitter.Status> listOfStatuses;
	private Intent updaterIntent;
	private Integer maxMsgStored;
	private Boolean automaticUpdateEnable;
	private AtomicInteger automaticUpdateInterval;
	private AsyncTask<Void, Void, Void> automaticUpdateAsyncTask;
	private final Integer onSuccessToken = 1;
	private General _this;
	
	
//Own methods and classes
	
	/**
	 * Fetch the updated value of {@code maxChars} and return it.
	 * 
	 * If the value {@code maxChars} is null (hasn't being initialized or the value in preferences
	 * was changed) fetches the most updated value from the preferences and return it.
	 * Otherwise (the value is initialized and the preferences didn't changed) simply return the
	 * value.
	 * 
	 * @return The updated value of {@code maxChars}.
	 * */
	public int getMaxChars()
	{
		if(maxChars == null)
		{
			String value = prefs.getString(STATUS_LENGTH_PREF_KEY, MAXIMUM_STATUS_LENGTH.toString());
			if(value.isEmpty())
			{
				maxChars = MAXIMUM_STATUS_LENGTH;
			}
			else
			{
				maxChars = Integer.valueOf(value);
			}
		}
		return maxChars;
	}
	
	/**
	 * Reset the value of {@code maxChars}.
	 * 
	 * One case of its use is when the value in preferences was changed. It set the reference to
	 * null indicating it have to be fetched from the preferences the next time it is needed.
	 * */
	public void resetMaxChars()
	{
		maxChars = null;
	}
	
	/**
	 * Fetch the updated value of {@code maxMsg} and return it.
	 * 
	 * If the value {@code maxMsg} is null (hasn't being initialized or the value in preferences
	 * was changed) fetches the most updated value from the preferences and return it.
	 * Otherwise (the value is initialized and the preferences didn't changed) simply return the
	 * value.
	 * 
	 * @return The updated value of {@code maxMsg}.
	 * */
	public int getMaxMsgToShow()
	{
		if(maxMsgToShow == null)
		{
			String value = prefs.getString(MSG_NUMBER_TO_SHOW_PREF_KEY, DEFAULT_MSG_TO_SHOW_NUMBER.toString());
			if(value.isEmpty())
			{
				maxMsgToShow = DEFAULT_MSG_TO_SHOW_NUMBER;
			}
			else
			{
				maxMsgToShow = Integer.valueOf(value);
			}
		}
		return maxMsgToShow;
	}
	
	/**
	 * Reset the value of {@code maxMsg}.
	 * 
	 * One case of its use is when the value in preferences was changed. It set the reference to
	 * null indicating it have to be fetched from the preferences the next time it is needed.
	 * */
	public void resetMaxMsgToShow()
	{
		maxMsgToShow = null;
	}
	
	/**
	 * Fetch the updated value of {@code maxMsgStored} and return it.
	 * 
	 * If the value {@code maxMsgStored} is null (hasn't being initialized or the value in preferences
	 * was changed) fetches the most updated value from the preferences and return it.
	 * Otherwise (the value is initialized and the preferences didn't changed) simply return the
	 * value.
	 * 
	 * @return The updated value of {@code maxMsgStored}.
	 * */
	public int getMaxMsgStored()
	{
		if(maxMsgStored == null)
		{
			String value = prefs.getString(MSG_NUMBER_TO_STORE_PREF_KEY, DEFAULT_MSG_TO_SHOW_NUMBER.toString());
			if(value.isEmpty())
			{
				maxMsgStored = DEFAULT_MSG_TO_STORE_NUMBER;
			}
			else
			{
				maxMsgStored = Integer.valueOf(value);
			}
		}
		return maxMsgStored;
	}
	
	/**
	 * Reset the value of {@code maxMsgStored}.
	 * 
	 * One case of its use is when the value in preferences was changed. It set the reference to
	 * null indicating it have to be fetched from the preferences the next time it is needed.
	 * */
	public void resetMaxMsgStored()
	{
		maxMsgStored = null;
	}
	
	/**
	 * Fetch the boolean value indicating whether the automatic updates are enable or not.
	 * 
	 * If the value {@code automaticUpdateEnable} is null, then a new value is fetched from the
	 * preferences and stored. Otherwise is returned the actual value.
	 * Always retrieves the most updated value from preferences.
	 * */
	public boolean getAutomaticUpdate()
	{
		if(automaticUpdateEnable == null)
		{
			automaticUpdateEnable = prefs.getBoolean(AUTOMATIC_UPDATE_PREF_KEY, false);
		}
		return automaticUpdateEnable;
	}
	
	/**
	 * Reset the value of {@code automaticUpdateEnable} and {@code automaticUpdateInterval}.
	 * 
	 * This flags that next time the variable {@code automaticUpdateEnable} is accessed, needs to
	 * be fetched from the preferences.
	 * Also reset the automatic updates interval because it won't be used.
	 * 
	 * @see #resetAutomaticUpdateInterval()
	 * */
	public void resetAutomaticUpdate()
	{
		automaticUpdateEnable = null;
		resetAutomaticUpdateInterval();
	}
	
	/**
	 * Fetch the value of {@code automaticUpdateInterval} and return it.
	 * 
	 * If the value {@code automaticUpdateInterval} is zero (hasn't being initialized or the 
	 * value in preferences was changed) fetches the most updated value from the preferences and
	 * return it. Otherwise (the value is initialized and the preferences didn't changed) simply
	 * return the value.
	 * */
	public Integer getAutomaticUpdateInterval()
	{
		if(automaticUpdateInterval.get() == 0)
		{
			String aux = prefs.getString(AUTOMATIC_UPDATE_PERIOD_PREF_KEY, DEFAULT_AUTOMATIC_UPDATE_INTERVAL.toString());
			if(aux.isEmpty())
			{
				automaticUpdateInterval.set(DEFAULT_AUTOMATIC_UPDATE_INTERVAL);
			}
			else
			{
				automaticUpdateInterval.set(Integer.valueOf(aux));
			}
		}
		if(automaticUpdateInterval.get() == 0)
		{
			automaticUpdateInterval.set(DEFAULT_AUTOMATIC_UPDATE_INTERVAL);
		}
		return automaticUpdateInterval.get();
	}
	
	/**
	 * Reset the value of {@code automaticUpdateInterval}.
	 * 
	 * One case of its use is when the value in preferences was changed. It set the value to
	 * zero indicating it have to be fetched from the preferences the next time it is needed.
	 * */
	public void resetAutomaticUpdateInterval()
	{
		automaticUpdateInterval.set(0);
	}
	
	/**
	 * Initialize and return the {@code twitter} object.
	 * If the {@code twitter} hasn't being initiated yet or if the preferences were changed,
	 * the object is initialized, otherwise simply return the existing reference.
	 * This behavior is justified with the change of login preferences (user, password
	 * and URL) that can happen any time during the application lifetime and those
	 * changes can invalidate the {@code twitter} connection. This way, every time an Activity
	 * needs the {@code twitter} object, this method is called and the preferences changes
	 * can be reflected here.
	 * Also sets the maximum number of status to be fetched in one request, according to the value
	 * number of messages to store, from the preferences.
	 * 
	 * @see #getMaxMsgStored()
	 * @return The {@code twitter} reference already initiated with the latest preferences.
	 * */
	public Twitter getTwitter()
	{
		if(twitter == null)
		{
			twitter = new Twitter(prefs.getString(USER_PREF_KEY, EMPTY_STRING), prefs.getString(PASSWORD_PREF_KEY, EMPTY_STRING));
			twitter.setAPIRootUrl(prefs.getString(URL_PREF_KEY, EMPTY_STRING));
			twitter.setMaxResults(getMaxMsgStored());
		}
		return twitter;
	}
	
	/**
	 * Reset the value of {@code twitter}.
	 * One case of its use is when the values in preferences were changed. It set the reference to
	 * null indicating that the object must be created again but with new values fetched from the 
	 * preferences, the next time the reference is needed.
	 * */
	public void resetTwitter()
	{
		twitter = null;
	}
	
	/**
	 * Verify if the user name, password and API URL preferences are filled.
	 * Checks if the values are empty or null.
	 * 
	 * @return True if none of the values are empty or null. Otherwise return false.
	 * */
	public boolean assertPreferences()
	{
		String user = prefs.getString(USER_PREF_KEY, null); 
		String password = prefs.getString(PASSWORD_PREF_KEY, null); 
		String url = prefs.getString(URL_PREF_KEY, null);
		if( user == null || user.isEmpty() ||
			password == null || password.isEmpty() ||
			url == null || url.isEmpty())
		{
			return false;
		}
		return true;
	}
	
	/**
	 * Displays a Toast element on the screen.
	 * 
	 * @param message Is the message that will be displayed.
	 * @param duration is the duration the Toast element will last in the display.
	 * */
	public void showToast(String message, int duration)
	{
		Toast.makeText(getApplicationContext(), message, duration).show();
	}
	
	/**
	 * Displays a Toast element on the screen.
	 * 
	 * @param messageID Is the id of the message that will be fetch from resources and displayed.
	 * @param duration is the duration the Toast element will last in the display.
	 * */
	public void showToast(int messageID, int duration)
	{
		Toast.makeText(getApplicationContext(), messageID, duration).show();
	}
	
	/**
	 * Logs one message in the application log with the specific tag.
	 * 
	 * @param message Is the message to log.
	 * */
	public void log(String message)
	{
		if(message != null)
		{
			Log.v(TAG, message);
		}
	}
	
	/**
	 * Start the asynchronous task that will periodically update the timeline private list.
	 * 
	 * First verifies if the automatic update preference is enable with the 
	 * {@code getAutomaticUpdate()} call and also if the task is already running. If so, return,
	 * otherwise runs the asynchronous task.
	 * */
	public void startAutomaticUpdate()
	{
		if(!getAutomaticUpdate() || (automaticUpdateAsyncTask != null && !automaticUpdateAsyncTask.isCancelled()) )
		{
			return;
		}
		automaticUpdateAsyncTask = new AsyncTask<Void, Void, Void>() 
		{
			/**
			 * Forever (almost) running task that acts like a timer to periodically update the timeline private list of statuses.
			 * 
			 * Fetch the interval of updates with the {@code getAutomaticUpdateInterval()} call and sleeps the thread for
			 * that period. When it awakes, checks if someone cancelled this task so it won't be a non terminate task that
			 * lasts forever.
			 * Send an intent to the TimelinePullService to update the timeline private list of statuses.
			 * In that intent is put one token to be delivered by the service once this one notifies this General instance.
			 * Also there is stored one reference for this instance in the {@code toNotifylist} so the service can know
			 * who is waiting to be notified.
			 * 
			 * This method is related to the {@code onSuccess(Integer)} method since that is the one that will be called from
			 * the service.
			 * 
			 * @see General#getAutomaticUpdateInterval()
			 * @see General#onSuccess(Integer)
			 * */
			@Override
			protected Void doInBackground(Void... params) 
			{
				try
				{
					while(true)
					{
						log("General.automaticUpdateAsyncTask: In sleep."); //TODO Delete after delivery
						SystemClock.sleep(getAutomaticUpdateInterval()*CONST_ONE_MIN_IN_MILIS);
						log("General.automaticUpdateAsyncTask: Out of sleep"); //TODO Delete after delivery
						if(isCancelled())
						{
							break;
						}
						if(updaterIntent == null)
						{
							updaterIntent = new Intent(getApplicationContext(), TimelinePullService.class);
						}
						toNotifyList.add(_this);
						updaterIntent.putExtra(TimelinePullService.ARG_TOKEN, onSuccessToken);
						startService(updaterIntent);
						log("General.automaticUpdateAsyncTask: TimelinePullService intent sent.");
						updaterIntent.removeExtra(TimelinePullService.ARG_TOKEN);
					}
				}
				catch(Exception e)
				{
					log("General.automaticUpdateAsyncTask: Some exception was thrown.");
					log(e.getMessage());
				}
				return null;
			}
		}.execute();
	}

	/**
	 * Stop the asynchronous task that is periodically updating the timeline private list.
	 * 
	 * If the task is still running, it is cancelled and unreferenced (to help GC).
	 * The service is stopped if it was started before and it is removed the the reference of this
	 * instance from the {@code toNotifyList} because it could been added or not.
	 * */
	public void stopAutomaticUpdate()
	{
		if(automaticUpdateAsyncTask != null && !automaticUpdateAsyncTask.isCancelled())
		{ 
			automaticUpdateAsyncTask.cancel(true);
			automaticUpdateAsyncTask = null;
			if(updaterIntent != null)
			{
				stopService(updaterIntent);
				updaterIntent = null;
			}
			toNotifyList.remove(_this);
		}
	}

	
//Application methods
	
	/**
	 * Initialize a reference to the shared preferences of this application and some variables.
	 * 
	 * Stores a reference to this instance ({@code _this}) so i can be used inside the
	 * {@code automaticUpdateAsyncTask} and a reference to the shared preferences so when the activities
	 * would be created this reference is already easily available.
	 * Also initializes the variables {@code listOfStatuses}, {@code toNotifyList} and 
	 * {@code automaticUpdateInterval}.
	 * */
	@Override
	public void onCreate()
	{
		_this = this;
		prefs = PreferenceManager.getDefaultSharedPreferences(this);
		listOfStatuses = new LinkedList<Twitter.Status>();
		toNotifyList = new ConcurrentLinkedQueue<IMyListener>();
		automaticUpdateInterval = new AtomicInteger(0);
	}
	
	
//IMyListener methods
	
	/**
	 * Perform some actions due to the service execution success.
	 * 
	 * When the service successfully fetches the timeline, an instance of this class should be
	 * removed from the {@code toNotifyList}.
	 * */
	@Override
	public void onSuccess(Integer token)
	{
		if(token != onSuccessToken)
		{
			return;
		}
		toNotifyList.remove(this);	
	}
	
}
